package com.googlecode.iant.internal.dom;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import org.springframework.util.Assert;
import org.springframework.util.xml.DomUtils;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import com.googlecode.iant.dom.IXmlAttribute;
import com.googlecode.iant.dom.IXmlElement;
import com.googlecode.iant.dom.UndefinedAttributeException;
import com.googlecode.iant.dom.UndefinedElementException;

public final class XmlElement implements IXmlElement {

	private Element element;

	public XmlElement(Element element) {
		Assert.notNull(element);
		this.element = element;
	}

	@Override
	public String getName() {
		return element.getLocalName();
	}

	public IXmlAttribute attribute(String name) {
		return attribute(name, false);
	}

	public IXmlAttribute attribute(String name, boolean required) {
		if (!element.hasAttribute(name)) {
			if (required) {
				throw new UndefinedAttributeException(name);
			} else {
				return UNDEFINED_ATTRIBUTE;
			}
		}
		return new XmlAttribute(element.getAttributeNode(name));
	}

	public IXmlElement element(String name) {
		return element(name, false);
	}

	public IXmlElement element(String name, boolean required) {
		Element child = DomUtils.getChildElementByTagName(element, name);
		if (child != null) {
			return new XmlElement(child);
		} else if (required) {
			throw new UndefinedElementException(name);
		} else {
			return UNDEFINED_ELEMENT;
		}
	}

	@SuppressWarnings("unchecked")
	@Override
	public List<IXmlElement> list(String name) {
		final List<Element> domElements = DomUtils.getChildElementsByTagName(
				element, name);
		final List<IXmlElement> elements = new ArrayList<IXmlElement>(
				domElements.size());
		for (Element element : domElements) {
			elements.add(new XmlElement(element));
		}
		return elements;
	}

	@Override
	public String getNamespace() {
		return element.getNamespaceURI();
	}

	@Override
	public List<IXmlElement> list() {
		final NodeList childNodes = element.getChildNodes();
		final List<Element> domElements = new ArrayList<Element>();
		for (int i = 0; i < childNodes.getLength(); i++) {
			final Node item = childNodes.item(i);
			if (item.getNodeType() == Node.ELEMENT_NODE) {
				domElements.add((Element) item);
			}
		}
		final List<IXmlElement> elements = new ArrayList<IXmlElement>(
				domElements.size());
		for (Element element : domElements) {
			elements.add(new XmlElement(element));
		}
		return elements;
	}

	@Override
	public String value() {
		return DomUtils.getTextValue(element);
	}
	
	@Override
	public boolean isDefined() {
		return true;
	}
	
	/**
	 * Singleton instance of {@link IXmlElement} that stands for
	 * a undefined element.
	 */
	private final IXmlElement UNDEFINED_ELEMENT = new IXmlElement () {

		@Override
		public IXmlAttribute attribute(String name) {
			return UNDEFINED_ATTRIBUTE;
		}

		@Override
		public IXmlAttribute attribute(String name, boolean required) {
			throw new UndefinedAttributeException(name);
		}

		@Override
		public IXmlElement element(String string) {
			return UNDEFINED_ELEMENT;
		}
		
		@Override
		public List<IXmlElement> list(String name) {
			return Collections.emptyList();
		}

		@Override
		public IXmlElement element(String name, boolean required) {
			throw new UndefinedElementException(name);
		}

		@Override
		public String getNamespace() {
			return "";
		}

		@Override
		public List<IXmlElement> list() {
			return Collections.emptyList();
		}

		@Override
		public String value() {
			return null;
		}

		@Override
		public String getName() {
			return null;
		}
		
		@Override
		public boolean isDefined() {
			return false;
		}
		
	};
	
	/**
	 * Singleton instance of {@link IXmlAttribute} that stands for
	 * a undefined attribute.
	 */
	private final IXmlAttribute UNDEFINED_ATTRIBUTE = new IXmlAttribute() {

		public <T> T as(Class<T> type) {
			return null;
		}

		public boolean defined() {
			return false;
		}

		public String value() {
			return null;
		}
		
	};

}